﻿using System;
using System.Collections.Generic;
using System.Linq;
using G = System.Drawing;
using GI = System.Drawing.Imaging;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using Text = QuickFont;

namespace OpenGameKit
{
    public static class Graphics
    {
        private static G.Color LineColor = G.Color.White;
        private static G.Color FillColor = G.Color.White;
        private static float LineSize = 1;
        private static int TextBitmapWidth = Game.Width;
        private static int TextBitmapHeight = Game.Height;
        private static bool IsDrawing = false;

        /// <summary>
        /// Set the color that will be used for drawing lines and borders.
        /// </summary>
        /// <param name="c">The color to use.</param>
        public static void SetLineColor(Color c)
        {
            LineColor = G.Color.FromArgb(c.A, c.R, c.G, c.B);
        }

        /// <summary>
        /// Set the color that will be used for filling shapes.
        /// </summary>
        /// <param name="c">The color to use.</param>
        public static void SetFillColor(Color c)
        {
            FillColor = G.Color.FromArgb(c.A, c.R, c.G, c.B);
        }

        /// <summary>
        /// Set the width of the line or border.
        /// </summary>
        /// <param name="i"></param>
        public static void SetLineSize(float i)
        {
            LineSize = i;
        }

        /// <summary>
        /// Clears the screen.
        /// </summary>
        public static void Clear()
        {
            GL.ClearColor(FillColor);
            GL.Clear(ClearBufferMask.ColorBufferBit);
            // Reset the clear color back to what it was.
            GL.ClearColor(G.Color.FromArgb(Game.CurrentScene.BackColor.A,
                Game.CurrentScene.BackColor.R,
                Game.CurrentScene.BackColor.G,
                Game.CurrentScene.BackColor.B));
        }

        /// <summary>
        /// Draw a line with the given parameters.
        /// </summary>
        /// <param name="x1">The x location of the first point.</param>
        /// <param name="y1">The y location of the first point.</param>
        /// <param name="x2">The x location of the second point.</param>
        /// <param name="y2">The y location of the second point.</param>
        public static void DrawLine(int x1, int y1, int x2, int y2)
        {
            GL.LineWidth(LineSize);

            GL.Begin(BeginMode.Lines);

            GL.Color3(LineColor);
            GL.Vertex2(x1, y1);
            GL.Vertex2(x2, y2);

            GL.End();
        }

        /// <summary>
        /// Draw a circle with the given parameters.
        /// </summary>
        /// <param name="x">The x location.</param>
        /// <param name="y">The y location.</param>
        /// <param name="radius">The radius of the circle.</param>
        /// <param name="border">If the border should be drawn around the circle.</param>
        /// <param name="fill">If the circle should be filled with a color.</param>
        public static void DrawCircle(int x, int y, int radius, bool border = false, bool fill = true)
        {
            if (fill == true)
            {
                GL.Begin(BeginMode.TriangleFan);

                GL.Color3(FillColor);
                GL.Vertex2(x, y);

                for (int angle = 0; angle < 360; angle++)
                {
                    GL.Vertex2(x + (float)Math.Sin(angle) * radius, y + Math.Cos(angle) * radius);
                }

                GL.End();
            }

            if (border == true)
            {
                GL.LineWidth(LineSize);
                GL.Begin(BeginMode.LineLoop);
                GL.Color3(LineColor);

                for (int angle = 0; angle < 360; angle++)
                {
                    GL.Vertex2(x + (float)Math.Sin(angle) * radius, y + Math.Cos(angle) * radius);
                }

                GL.End();
            }
        }

        /// <summary>
        /// Draw a rectangle to the screen with the given parameters.
        /// </summary>
        /// <param name="x">The top left x location of the rectangle.</param>
        /// <param name="y">The top< left y location of the rectangle./param>
        /// <param name="width">The width of the rectangle.</param>
        /// <param name="height">The height of the rectangle.</param>
        /// <param name="border">If the border shoulde be drawn.</param>
        /// <param name="fill">If the rectangle should be filled in.</param>
        public static void DrawRectangle(int x, int y, int width, int height, bool border = false, bool fill = true)
        {
            if (fill == true)
            {
                GL.Begin(BeginMode.Polygon);
                GL.Color3(FillColor);
                GL.Vertex2(x, y);
                GL.Vertex2(x + width, y);
                GL.Vertex2(x + width, y + height);
                GL.Vertex2(x, y + height);

                GL.End();
            }

            if (border == true)
            {
                GL.LineWidth(LineSize);
                GL.Begin(BeginMode.LineLoop);
                GL.Color3(LineColor);
                GL.Vertex2(x, y);
                GL.Vertex2(x + width, y);
                GL.Vertex2(x + width, y + height);
                GL.Vertex2(x, y + height);

                GL.End();
            }
        }

        /// <summary>
        /// Loads a image from a file and returns the ID.
        /// </summary>
        /// <param name="file">The file to load from.</param>
        /// <param name="transparent">The color that is drawn as transparent.</param>
        /// <returns></returns>
        public static int LoadImage(string file, Color transparent)
        {
            try
            {
                G.Bitmap B = new G.Bitmap(file);
                B.MakeTransparent(G.Color.FromArgb(transparent.A, transparent.R, transparent.G, transparent.B));

                int Tex;
                GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest);

                GL.GenTextures(1, out Tex);
                GL.BindTexture(TextureTarget.Texture2D, Tex);

                GI.BitmapData Data = B.LockBits(new G.Rectangle(0, 0, B.Width, B.Height), GI.ImageLockMode.ReadOnly,
                    GI.PixelFormat.Format32bppPArgb);

                GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, Data.Width, Data.Height, 0, PixelFormat.Bgra,
                    PixelType.UnsignedByte, Data.Scan0);
                B.UnlockBits(Data);

                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
                B.Dispose();

                return Tex;
            }
            catch (Exception)
            {
                Exception e = new Exception("Failed to load image from file.");
                throw e;
            }
        }

        /// <summary>
        /// When a image is no longer needed it can be disposed here.
        /// </summary>
        /// <param name="id">The ID of the image to use.</param>
        public static void DisposeImage(int id)
        {
            GL.DeleteTextures(1, ref id);
        }

        /// <summary>
        /// Returns the width of a image file.
        /// </summary>
        /// <param name="file">The file to check the width of.</param>
        /// <returns></returns>
        public static int GetImageWidth(string file)
        {
            // Slow but works.
            G.Bitmap B = new G.Bitmap(file);
            int i = B.Width;

            B.Dispose();
            return i;
        }

        /// <summary>
        /// Returns the height of a image file.
        /// </summary>
        /// <param name="file">The file to check the height of.</param>
        /// <returns></returns>
        public static int GetImageHeight(string file)
        {
            // Slow but works.
            G.Bitmap B = new G.Bitmap(file);
            int i = B.Height;

            B.Dispose();
            return i;
        }

        /// <summary>
        /// Draws a image to the screen with the given texture ID.
        /// </summary>
        /// <param name="image">The ID of the image to use.</param>
        /// <param name="x">The top left x location.</param>
        /// <param name="y">The top left y location.</param>
        /// <param name="width">The width of the image.</param>
        /// <param name="height">The height of the image.</param>
        public static void DrawImage(int image, int x, int y, int width, int height)
        {
            DrawImage(image, x, y, width, height, 0);
        }

        /// <summary>
        /// Draws a image to the screen with the given texture ID.
        /// </summary>
        /// <param name="image">The ID of the image to use.</param>
        /// <param name="x">The top left x location.</param>
        /// <param name="y">The top left y location.</param>
        /// <param name="width">The width of the image.</param>
        /// <param name="height">The height of the image.</param>
        /// <param name="angle">The angle to draw the image at.</param>
        public static void DrawImage(int image, int x, int y, int width, int height, double angle)
        {
            GL.MatrixMode(MatrixMode.Modelview);
            GL.PushMatrix();
            GL.LoadIdentity();

            GL.Translate(x, y, 0);
            GL.Rotate(angle, 0, 0, 1);

            GL.Enable(EnableCap.Texture2D);

            GL.BindTexture(TextureTarget.Texture2D, image);

            GL.Begin(BeginMode.Quads);

            GL.Color3(G.Color.Transparent);
            GL.TexCoord2(0, 0);
            GL.Vertex2(-width / 2, -height / 2);

            GL.TexCoord2(1, 0);
            GL.Vertex2(width / 2, -height / 2);

            GL.TexCoord2(1, 1);
            GL.Vertex2(width / 2, height / 2);

            GL.TexCoord2(0, 1);
            GL.Vertex2(-width / 2, height / 2);

            GL.End();


            GL.Disable(EnableCap.Texture2D);
            GL.PopMatrix();
        }

        /// <summary>
        /// Start drawing a shape or set of lines, use the Element method to draw.
        /// </summary>
        /// <param name="dm"></param>
        public static void StartMode(DrawMode dm, float lineSize = 1)
        {
            if (IsDrawing == false)
            {
                IsDrawing = true;
                GL.LineWidth(lineSize);

                switch (dm)
                {
                    case DrawMode.Line:
                        GL.Begin(BeginMode.LineStrip);
                        break;
                    case DrawMode.Shape:
                        GL.Begin(BeginMode.Polygon);
                        break;
                }
            }
            else
            {
                Exception e = new Exception("StartMode was called again before a EndMode was called.");
                throw e;
            }
        }

        /// <summary>
        /// Draw something after you call StartMode.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="c"></param>
        public static void Element(int x, int y, Color c)
        {
            GL.Color3(G.Color.FromArgb(c.A, c.R, c.G, c.B));
            GL.Vertex2(x, y);
        }

        /// <summary>
        /// Finish drawing a shape or set of lines. Must be called for graphics to work.
        /// </summary>
        public static void EndMode()
        {
            if (IsDrawing == true)
            {
                GL.End();
                IsDrawing = false;
            }
            else
            {
                Exception e = new Exception("EndMode was called but a StartMode was never called.");
                throw e;
            }
        }

        public enum DrawMode
        {
            Line,
            Shape,
        }

        /// <summary>
        /// Create a font file that is loaded from a .ttf and saved as a QuickFont file and a .png.
        /// </summary>
        /// <param name="id">The font's id to use.</param>
        /// <param name="fontFile">The .ttf font to load.</param>
        /// <param name="fontSize">The size of the text.</param>
        public static void CreateFont(int id, string fontFile, int fontSize)
        {
            Text.QFont.CreateTextureFontFiles(fontFile, fontSize, "EngineData/Fonts/" + id.ToString());
        }

        /// <summary>
        /// Draw text to the screen with the given font.
        /// </summary>
        /// <param name="id">The created font to use.</param>
        /// <param name="text">The text to draw.</param>
        /// <param name="x">X location.</param>
        /// <param name="y">Y location.</param>
        /// <param name="c">The color to draw the text in.</param>
        public static void DrawText(int id, string text, int x, int y, Color c)
        {
            Text.QFont F = Text.QFont.FromQFontFile("EngineData/Fonts/" + id.ToString() + ".qfont");
            F.Options.Colour = new Color4(c.R, c.G, c.B, c.A);
            F.Print(text, new Vector2(x, y));

            F.Dispose();
        }
    }
}
